/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package endpoint

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/core"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"
	commtls "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/util/comm/tls"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/util/endpoint"
	x509GM "github.com/tjfoc/gmsm/x509"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/errors/retry"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/errors/status"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/logging"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/core/config/lookup"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/util/pathvar"
	"github.com/mitchellh/mapstructure"
	"github.com/pkg/errors"
	"github.com/tjfoc/gmsm/gmtls"
	grpcCodes "google.golang.org/grpc/codes"
)

var logger = logging.NewLogger("ichainsdk/ichain")
var defaultConsenserListenPort = 7050
var defaultInodeListenPort = 7051

const (
	defaultInodeConnectionTimeout         = time.Second * 10
	defaultInodeResponseTimeout           = time.Minute * 3
	defaultDiscoveryGreylistExpiryTimeout = time.Second * 10
	defaultEventRegTimeout                = time.Second * 15
	defaultConsenserConnectionTimeout     = time.Second * 15
	defaultConsenserResponseTimeout       = time.Minute * 2
	defaultQueryTimeout                   = time.Minute * 3
	defaultExecuteTimeout                 = time.Minute * 3
	defaultResMgmtTimeout                 = time.Minute * 3
	defaultDiscoveryConnectionTimeout     = time.Second * 15
	defaultDiscoveryResponseTimeout       = time.Second * 15
	defaultConnIdleInterval               = time.Second * 30
	defaultEventServiceIdleInterval       = time.Minute * 2
	defaultChainConfigRefreshInterval     = time.Second * 90
	defaultChainMemshpRefreshInterval     = time.Second * 60
	defaultDiscoveryRefreshInterval       = time.Second * 5
	defaultSelectionRefreshInterval       = time.Second * 5
	defaultCacheSweepInterval             = time.Second * 15

	defaultResolverStrategy                 = ichain.PreferOrgStrategy
	defaultMinBlockHeightResolverMode       = ichain.ResolveByThreshold
	defaultBalancer                         = ichain.Random
	defaultBlockHeightLagThreshold          = 5
	defaultReconnectBlockHeightLagThreshold = 10
	defaultInodeMonitor                     = "Disabled" // The inode monitor will be enabled if necessary
	defaultInodeMonitorPeriod               = 5 * time.Second

	//default grpc opts
	defaultKeepAliveTime    = 0
	defaultKeepAliveTimeout = time.Second * 20
	defaultKeepAlivePermit  = false
	defaultFailFast         = false
	defaultAllowInsecure    = false

	defaultMaxTargets   = 2
	defaultMinResponses = 1

	defaultEntity = "_default"
)

var (
	defaultDiscoveryRetryableCodes = map[status.Group][]status.Code{
		status.GRPCTransportStatus: {
			status.Code(grpcCodes.Unavailable),
		},
		status.DiscoveryServerStatus: {
			status.QueryEndorsers,
		},
	}

	defaultDiscoveryRetryOpts = retry.Opts{
		Attempts:       6,
		InitialBackoff: 500 * time.Millisecond,
		MaxBackoff:     5 * time.Second,
		BackoffFactor:  1.75,
		RetryableCodes: defaultDiscoveryRetryableCodes,
	}

	defaultChainPolicies = &ChainPolicies{
		QueryChainConfig: QueryChainConfigPolicy{
			MaxTargets:   defaultMaxTargets,
			MinResponses: defaultMinResponses,
			RetryOpts:    retry.Opts{},
		},
		Discovery: DiscoveryPolicy{
			MaxTargets:   defaultMaxTargets,
			MinResponses: defaultMinResponses,
			RetryOpts:    defaultDiscoveryRetryOpts,
		},
		Selection: SelectionPolicy{
			SortingStrategy:         BlockHeightPriority,
			Balancer:                Random,
			BlockHeightLagThreshold: defaultBlockHeightLagThreshold,
		},
		EventService: EventServicePolicy{
			ResolverStrategy:                 string(ichain.PreferOrgStrategy),
			MinBlockHeightResolverMode:       string(defaultMinBlockHeightResolverMode),
			Balancer:                         Random,
			InodeMonitor:                     defaultInodeMonitor,
			InodeMonitorPeriod:               defaultInodeMonitorPeriod,
			BlockHeightLagThreshold:          defaultBlockHeightLagThreshold,
			ReconnectBlockHeightLagThreshold: defaultReconnectBlockHeightLagThreshold,
		},
	}
)

// ConfigFromBackend returns endpoint config implementation for given backend
func ConfigFromBackend(coreBackend ...core.ConfigBackend) (ichain.EndpointConfig, error) {

	config := &EndpointConfig{
		backend: lookup.New(coreBackend...),
	}

	if err := config.loadEndpointConfiguration(); err != nil {
		return nil, errors.WithMessage(err, "network configuration load failed")
	}

	//print deprecated warning
	detectDeprecatedNetworkConfig(config)

	return config, nil
}

type EndpointConfig struct {
	backend                *lookup.ConfigLookup
	networkConfig          *ichain.NetworkConfig
	tlsCertPool            commtls.CertPool
	entityMatchers         *entityMatchers
	inodeConfigsByOrg      map[string][]ichain.InodeConfig
	networkInodes          []ichain.NetworkInode
	consenserConfigs       []ichain.ConsenserConfig
	chainInodesByChain     map[string][]ichain.ChainInode
	chainConsensersByChain map[string][]ichain.ConsenserConfig
	tlsClientCerts         []gmtls.Certificate
	inodeMatchers          []matcherEntry
	consenserMatchers      []matcherEntry
	chainMatchers          []matcherEntry
	defaultInodeConfig     ichain.InodeConfig
	defaultConsenserConfig ichain.ConsenserConfig
	defaultChainPolicies   ichain.ChainPolicies
	defaultChain           *ichain.ChainEndpointConfig
}

type endpointConfigEntity struct {
	Client        ClientConfig
	Chains        map[string]ChainEndpointConfig
	Organizations map[string]OrganizationConfig
	Consensers    map[string]ConsenserConfig
	Inodes        map[string]InodeConfig
}

type entityMatchers struct {
	matchers map[string][]MatchConfig
}

type matcherEntry struct {
	regex       *regexp.Regexp
	matchConfig MatchConfig
}

func (c *EndpointConfig) Timeout(tType ichain.TimeoutType) time.Duration {
	return c.getTimeout(tType)
}

func (c *EndpointConfig) ConsensersConfig() []ichain.ConsenserConfig {
	return c.consenserConfigs
}

func (c *EndpointConfig) ConsenserConfig(nameOrURL string) (*ichain.ConsenserConfig, bool, bool) {
	return c.tryMatchingConsenserConfig(nameOrURL, true)
}

func (c *EndpointConfig) InodesConfig(org string) ([]ichain.InodeConfig, bool) {
	inodeConfigs, ok := c.inodeConfigsByOrg[strings.ToLower(org)]
	return inodeConfigs, ok
}

func (c *EndpointConfig) InodeConfig(nameOrURL string) (*ichain.InodeConfig, bool) {
	return c.tryMatchingInodeConfig(nameOrURL, true)
}

func (c *EndpointConfig) NetworkConfig() *ichain.NetworkConfig {
	return c.networkConfig
}

func (c *EndpointConfig) NetworkInodes() []ichain.NetworkInode {
	return c.networkInodes
}

func (c *EndpointConfig) mappedChainName(networkConfig *ichain.NetworkConfig, chainName string) string {

	// if chainName is the original key found in the Chains map config, then return it as is
	_, ok := networkConfig.Chains[strings.ToLower(chainName)]
	if ok {
		return chainName
	}

	// if !ok, then find a chainMatcher for chainName

	//Return if no chainMatchers are configured
	if len(c.chainMatchers) == 0 {
		return defaultEntity
	}

	//loop over chainMatchers to find the matching chain name
	for _, matcher := range c.chainMatchers {
		if matcher.regex.MatchString(chainName) {
			// get the matching matchConfig from the index number
			return matcher.matchConfig.MappedName
		}
	}

	return defaultEntity
}

// ChainConfig 返回链的配置
func (c *EndpointConfig) ChainConfig(name string) *ichain.ChainEndpointConfig {

	mappedChainName := c.mappedChainName(c.networkConfig, name)
	if mappedChainName == defaultEntity {
		return c.defaultChain
	}

	ch, ok := c.networkConfig.Chains[strings.ToLower(mappedChainName)]
	if !ok {
		return c.defaultChain
	}
	return &ch
}

func (c *EndpointConfig) ChainInodes(name string) []ichain.ChainInode {

	//get mapped chain name
	mappedChainName := c.mappedChainName(c.networkConfig, name)

	//look up in dictionary
	return c.chainInodesByChain[strings.ToLower(mappedChainName)]
}

func (c *EndpointConfig) ChainConsensers(name string) []ichain.ConsenserConfig {
	//get mapped chain name
	mappedChainName := c.mappedChainName(c.networkConfig, name)

	//look up in dictionary
	return c.chainConsensersByChain[strings.ToLower(mappedChainName)]
}

func (c *EndpointConfig) TLSCACertPool() commtls.CertPool {
	return c.tlsCertPool
}

func (c *EndpointConfig) TLSClientCerts() []gmtls.Certificate {
	return c.tlsClientCerts
}

func (c *EndpointConfig) loadPrivateKeyFromConfig(clientConfig *ClientConfig, cb []byte) ([]gmtls.Certificate, error) {
	kb := clientConfig.TLSCerts.Client.Key.Bytes()

	clientCerts, err := gmtls.X509KeyPair(cb, kb)
	if err != nil {
		return nil, errors.Errorf("Error loading gm cert/key pair as TLS client credentials: %s", err)
	}

	logger.Debug("pk read from config successfully")

	return []gmtls.Certificate{clientCerts}, nil
}

func (c *EndpointConfig) CryptoConfigPath() string {
	return pathvar.Subst(c.backend.GetString("client.cryptoconfig.path"))
}

func (c *EndpointConfig) getTimeout(tType ichain.TimeoutType) time.Duration { //nolint
	var timeout time.Duration
	switch tType {
	case ichain.InodeConnection:
		timeout = c.backend.GetDuration("client.inode.timeout.connection")
		if timeout == 0 {
			timeout = defaultInodeConnectionTimeout
		}
	case ichain.InodeResponse:
		timeout = c.backend.GetDuration("client.inode.timeout.response")
		if timeout == 0 {
			timeout = defaultInodeResponseTimeout
		}
	case ichain.DiscoveryGreylistExpiry:
		timeout = c.backend.GetDuration("client.inode.timeout.discovery.greylistExpiry")
		if timeout == 0 {
			timeout = defaultDiscoveryGreylistExpiryTimeout
		}
	case ichain.EventReg:
		timeout = c.backend.GetDuration("client.eventService.timeout.registrationResponse")
		if timeout == 0 {
			timeout = defaultEventRegTimeout
		}
	case ichain.ConsenserConnection:
		timeout = c.backend.GetDuration("client.consenser.timeout.connection")
		if timeout == 0 {
			timeout = defaultConsenserConnectionTimeout
		}
	case ichain.ConsenserResponse:
		timeout = c.backend.GetDuration("client.consenser.timeout.response")
		if timeout == 0 {
			timeout = defaultConsenserResponseTimeout
		}
	case ichain.DiscoveryConnection:
		timeout = c.backend.GetDuration("client.discovery.timeout.connection")
		if timeout == 0 {
			timeout = defaultDiscoveryConnectionTimeout
		}
	case ichain.DiscoveryResponse:
		timeout = c.backend.GetDuration("client.discovery.timeout.response")
		if timeout == 0 {
			timeout = defaultDiscoveryResponseTimeout
		}
	case ichain.Query:
		timeout = c.backend.GetDuration("client.global.timeout.query")
		if timeout == 0 {
			timeout = defaultQueryTimeout
		}
	case ichain.Execute:
		timeout = c.backend.GetDuration("client.global.timeout.execute")
		if timeout == 0 {
			timeout = defaultExecuteTimeout
		}
	case ichain.ResMgmt:
		timeout = c.backend.GetDuration("client.global.timeout.resmgmt")
		if timeout == 0 {
			timeout = defaultResMgmtTimeout
		}
	case ichain.ConnectionIdle:
		timeout = c.backend.GetDuration("client.global.cache.connectionIdle")
		if timeout == 0 {
			timeout = defaultConnIdleInterval
		}
	case ichain.EventServiceIdle:
		timeout = c.backend.GetDuration("client.global.cache.eventServiceIdle")
		if timeout == 0 {
			timeout = defaultEventServiceIdleInterval
		}
	case ichain.ChainConfigRefresh:
		timeout = c.backend.GetDuration("client.global.cache.chainConfig")
		if timeout == 0 {
			timeout = defaultChainConfigRefreshInterval
		}
	case ichain.ChainMembershipRefresh:
		timeout = c.backend.GetDuration("client.global.cache.chainMembership")
		if timeout == 0 {
			timeout = defaultChainMemshpRefreshInterval
		}
	case ichain.DiscoveryServiceRefresh:
		timeout = c.backend.GetDuration("client.global.cache.discovery")
		if timeout == 0 {
			timeout = defaultDiscoveryRefreshInterval
		}
	case ichain.SelectionServiceRefresh:
		timeout = c.backend.GetDuration("client.global.cache.selection")
		if timeout == 0 {
			timeout = defaultSelectionRefreshInterval
		}

	case ichain.CacheSweepInterval: // EXPERIMENTAL - do we need this to be configurable?
		timeout = c.backend.GetDuration("client.cache.interval.sweep")
		if timeout == 0 {
			timeout = defaultCacheSweepInterval
		}
	}

	return timeout
}

func (c *EndpointConfig) loadEndpointConfiguration() error {

	endpointConfigurationEntity := endpointConfigEntity{}

	err := c.backend.UnmarshalKey("client", &endpointConfigurationEntity.Client)
	logger.Debugf("Client is: %+v", endpointConfigurationEntity.Client)
	if err != nil {
		return errors.WithMessage(err, "failed to parse 'client' config item to endpointConfigurationEntity.Client type")
	}

	err = c.backend.UnmarshalKey(
		"chains", &endpointConfigurationEntity.Chains,
		lookup.WithUnmarshalHookFunction(inodeChainConfigHookFunc()),
	)
	logger.Debugf("chains are: %+v", endpointConfigurationEntity.Chains)
	if err != nil {
		return errors.WithMessage(err, "failed to parse 'chains' config item to endpointConfigurationEntity.Chains type")
	}

	err = c.backend.UnmarshalKey("organizations", &endpointConfigurationEntity.Organizations)
	logger.Debugf("organizations are: %+v", endpointConfigurationEntity.Organizations)
	if err != nil {
		return errors.WithMessage(err, "failed to parse 'organizations' config item to endpointConfigurationEntity.Organizations type")
	}

	err = c.backend.UnmarshalKey("consensers", &endpointConfigurationEntity.Consensers)
	logger.Debugf("consensers are: %+v", endpointConfigurationEntity.Consensers)
	logger.Infof("consensers are: %+v\n", endpointConfigurationEntity.Consensers)
	if err != nil {
		return errors.WithMessage(err, "failed to parse 'consensers' config item to endpointConfigurationEntity.Consensers type")
	}

	err = c.backend.UnmarshalKey("inodes", &endpointConfigurationEntity.Inodes)
	logger.Debugf("inodes are: %+v", endpointConfigurationEntity.Inodes)
	if err != nil {
		return errors.WithMessage(err, "failed to parse 'inodes' config item to endpointConfigurationEntity.Inodes type")
	}

	//load all endpointconfig entities
	err = c.loadEndpointConfigEntities(&endpointConfigurationEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load chain configs")
	}

	return nil
}

func (c *EndpointConfig) loadEndpointConfigEntities(configEntity *endpointConfigEntity) error {

	//Compile the entityMatchers
	matchError := c.compileMatchers()
	if matchError != nil {
		return matchError
	}

	//load all TLS configs, before building any network config
	err := c.loadAllTLSConfig(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load network TLSConfig")
	}

	//load default configs
	err = c.loadDefaultConfigItems(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to network config")
	}

	//load network config
	err = c.loadNetworkConfig(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to network config")
	}

	//load inode configs by org dictionary
	c.loadInodeConfigsByOrg()

	//load network inodes
	c.loadNetworkInodes()

	//load consenser configs
	err = c.loadConsenserConfigs()
	if err != nil {
		return errors.WithMessage(err, "failed to load consenser configs")
	}

	//load chain inodes
	err = c.loadChainInodes()
	if err != nil {
		return errors.WithMessage(err, "failed to load chain inodes")
	}

	//load chain consensers
	err = c.loadChainConsensers()
	if err != nil {
		return errors.WithMessage(err, "failed to load chain consensers")
	}

	//load tls cert pool
	err = c.loadTLSCertPool()
	if err != nil {
		return errors.WithMessage(err, "failed to load TLS cert pool")
	}

	c.loadDefaultChain()

	return nil
}

func (c *EndpointConfig) loadDefaultChain() {
	defChCfg, ok := c.networkConfig.Chains[defaultEntity]
	if ok {
		c.defaultChain = &ichain.ChainEndpointConfig{Inodes: defChCfg.Inodes, Consensers: defChCfg.Consensers, Policies: defChCfg.Policies}
		delete(c.networkConfig.Chains, defaultEntity)
	} else {
		logger.Debugf("No default config. Returning hard-coded defaults.")
		c.defaultChain = &ichain.ChainEndpointConfig{Policies: c.getChainPolicies(defaultChainPolicies)}
	}
}

func (c *EndpointConfig) loadDefaultConfigItems(configEntity *endpointConfigEntity) error {
	//default consenser config
	err := c.loadDefaultConsenser(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load default consenser")
	}

	//default inode config
	err = c.loadDefaultInode(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load default inode")
	}

	//default chain policies
	c.loadDefaultChainPolicies(configEntity)
	return nil
}

func (c *EndpointConfig) loadNetworkConfig(configEntity *endpointConfigEntity) error {

	networkConfig := ichain.NetworkConfig{}

	// 初始化链配置
	networkConfig.Chains = make(map[string]ichain.ChainEndpointConfig)

	defChNwCfg, ok := configEntity.Chains[defaultEntity]
	if ok {
		networkConfig.Chains[defaultEntity] = c.loadChainEndpointConfig(defChNwCfg, ChainEndpointConfig{})
	} else {
		networkConfig.Chains[defaultEntity] = ichain.ChainEndpointConfig{Policies: c.getChainPolicies(defaultChainPolicies)}
	}

	for chID, chNwCfg := range configEntity.Chains {
		if chID == defaultEntity {
			continue
		}

		networkConfig.Chains[chID] = c.loadChainEndpointConfig(chNwCfg, defChNwCfg)
	}

	// 读取组织配置
	networkConfig.Organizations = make(map[string]ichain.OrganizationConfig)
	for orgName, orgConfig := range configEntity.Organizations {

		tlsKeyCertPairs := make(map[string]ichain.CertKeyPair)
		for user, tlsKeyPair := range orgConfig.Users {
			tlsKeyCertPairs[user] = ichain.CertKeyPair{
				Cert: tlsKeyPair.Cert.Bytes(),
				Key:  tlsKeyPair.Key.Bytes(),
			}
		}

		networkConfig.Organizations[orgName] = ichain.OrganizationConfig{
			IAMID:                  orgConfig.IAMID,
			CryptoPath:             orgConfig.CryptoPath,
			Inodes:                 orgConfig.Inodes,
			CertificateAuthorities: orgConfig.CertificateAuthorities,
			Users:                  tlsKeyCertPairs,
		}

	}

	// 加载共识节点配置
	err := c.loadAllConsenserConfigs(&networkConfig, configEntity.Consensers)
	if err != nil {
		return err
	}

	// 加载inode节点配置
	err = c.loadAllInodeConfigs(&networkConfig, configEntity.Inodes)
	if err != nil {
		return err
	}

	c.networkConfig = &networkConfig
	return nil
}

func (c *EndpointConfig) loadChainEndpointConfig(chNwCfg ChainEndpointConfig, defChNwCfg ChainEndpointConfig) ichain.ChainEndpointConfig {

	chInodes := make(map[string]ichain.InodeChainConfig)

	chNwCfgInodes := chNwCfg.Inodes
	if len(chNwCfgInodes) == 0 {
		chNwCfgInodes = defChNwCfg.Inodes
	}

	for chInode, chInodeCfg := range chNwCfgInodes {
		if c.isInodeToBeIgnored(chInode) {
			continue
		}
		chInodes[chInode] = ichain.InodeChainConfig{
			EndorsingInode: chInodeCfg.EndorsingInode,
			ContractQuery:  chInodeCfg.ContractQuery,
			LedgerQuery:    chInodeCfg.LedgerQuery,
			EventSource:    chInodeCfg.EventSource,
		}
	}

	chConsensers := []string{}

	chNwCfgConsensers := chNwCfg.Consensers
	if len(chNwCfgConsensers) == 0 {
		//fill consensers in with default chain consensers
		chNwCfgConsensers = defChNwCfg.Consensers
	}

	for _, name := range chNwCfgConsensers {
		if !c.isConsenserToBeIgnored(name) {
			//filter consenser to be ignored
			chConsensers = append(chConsensers, name)
		}
	}

	// Policies use default chain policies if info is missing
	return ichain.ChainEndpointConfig{
		Inodes:     chInodes,
		Consensers: chConsensers,
		Policies:   c.addMissingChainPoliciesItems(chNwCfg),
	}
}

func (c *EndpointConfig) getChainPolicies(policies *ChainPolicies) ichain.ChainPolicies {

	discoveryPolicy := ichain.DiscoveryPolicy{

		MaxTargets:   policies.Discovery.MaxTargets,
		MinResponses: policies.Discovery.MinResponses,
		RetryOpts:    policies.Discovery.RetryOpts,
	}

	selectionPolicy := ichain.SelectionPolicy{

		SortingStrategy:         ichain.SelectionSortingStrategy(policies.Selection.SortingStrategy),
		Balancer:                ichain.BalancerType(policies.Selection.Balancer),
		BlockHeightLagThreshold: policies.Selection.BlockHeightLagThreshold,
	}

	chainCfgPolicy := ichain.QueryChainConfigPolicy{
		MaxTargets:   policies.QueryChainConfig.MaxTargets,
		MinResponses: policies.QueryChainConfig.MinResponses,
		RetryOpts:    policies.QueryChainConfig.RetryOpts,
	}

	eventServicePolicy := ichain.EventServicePolicy{
		ResolverStrategy:                 ichain.ResolverStrategy(policies.EventService.ResolverStrategy),
		MinBlockHeightResolverMode:       ichain.MinBlockHeightResolverMode(policies.EventService.MinBlockHeightResolverMode),
		Balancer:                         ichain.BalancerType(policies.EventService.Balancer),
		BlockHeightLagThreshold:          policies.EventService.BlockHeightLagThreshold,
		InodeMonitor:                     ichain.EnabledDisabled(policies.EventService.InodeMonitor),
		ReconnectBlockHeightLagThreshold: policies.EventService.ReconnectBlockHeightLagThreshold,
		InodeMonitorPeriod:               policies.EventService.InodeMonitorPeriod,
	}

	return ichain.ChainPolicies{
		Discovery:        discoveryPolicy,
		Selection:        selectionPolicy,
		QueryChainConfig: chainCfgPolicy,
		EventService:     eventServicePolicy,
	}
}

func (c *EndpointConfig) addMissingChainPoliciesItems(chNwCfg ChainEndpointConfig) ichain.ChainPolicies {

	policies := c.getChainPolicies(&chNwCfg.Policies)

	policies.Discovery = c.addMissingDiscoveryPolicyInfo(policies.Discovery)
	policies.Selection = c.addMissingSelectionPolicyInfo(policies.Selection)
	policies.QueryChainConfig = c.addMissingQueryChainConfigPolicyInfo(policies.QueryChainConfig)
	policies.EventService = c.addMissingEventServicePolicyInfo(policies.EventService)

	return policies
}

func (c *EndpointConfig) addMissingDiscoveryPolicyInfo(policy ichain.DiscoveryPolicy) ichain.DiscoveryPolicy {

	if policy.MaxTargets == 0 {
		policy.MaxTargets = c.defaultChainPolicies.Discovery.MaxTargets
	}

	if policy.MinResponses == 0 {
		policy.MinResponses = c.defaultChainPolicies.Discovery.MinResponses
	}

	if isEmpty(policy.RetryOpts) {
		policy.RetryOpts = c.defaultChainPolicies.Discovery.RetryOpts
	} else {
		policy.RetryOpts = addMissingRetryOpts(policy.RetryOpts, c.defaultChainPolicies.Discovery.RetryOpts)
	}

	return policy
}

func (c *EndpointConfig) addMissingSelectionPolicyInfo(policy ichain.SelectionPolicy) ichain.SelectionPolicy {

	if policy.SortingStrategy == "" {
		policy.SortingStrategy = c.defaultChainPolicies.Selection.SortingStrategy
	}

	if policy.Balancer == "" {
		policy.Balancer = c.defaultChainPolicies.Selection.Balancer
	}

	if policy.BlockHeightLagThreshold == 0 {
		policy.BlockHeightLagThreshold = defaultBlockHeightLagThreshold
	}

	return policy
}

func (c *EndpointConfig) addMissingQueryChainConfigPolicyInfo(policy ichain.QueryChainConfigPolicy) ichain.QueryChainConfigPolicy {

	if policy.MaxTargets == 0 {
		policy.MaxTargets = c.defaultChainPolicies.QueryChainConfig.MaxTargets
	}

	if policy.MinResponses == 0 {
		policy.MinResponses = c.defaultChainPolicies.QueryChainConfig.MinResponses
	}

	if isEmpty(policy.RetryOpts) {
		policy.RetryOpts = c.defaultChainPolicies.QueryChainConfig.RetryOpts
	} else {
		policy.RetryOpts = addMissingRetryOpts(policy.RetryOpts, c.defaultChainPolicies.QueryChainConfig.RetryOpts)
	}

	return policy
}

func (c *EndpointConfig) addMissingEventServicePolicyInfo(policy ichain.EventServicePolicy) ichain.EventServicePolicy {
	if policy.Balancer == "" {
		policy.Balancer = c.defaultChainPolicies.EventService.Balancer
	}
	if policy.BlockHeightLagThreshold == 0 {
		policy.BlockHeightLagThreshold = c.defaultChainPolicies.EventService.BlockHeightLagThreshold
	}
	if policy.ResolverStrategy == "" {
		policy.ResolverStrategy = c.defaultChainPolicies.EventService.ResolverStrategy
	}
	if policy.MinBlockHeightResolverMode == "" {
		policy.MinBlockHeightResolverMode = c.defaultChainPolicies.EventService.MinBlockHeightResolverMode
	}
	if policy.InodeMonitor == "" {
		policy.InodeMonitor = c.defaultChainPolicies.EventService.InodeMonitor
	}
	if policy.ReconnectBlockHeightLagThreshold == 0 {
		policy.ReconnectBlockHeightLagThreshold = c.defaultChainPolicies.EventService.ReconnectBlockHeightLagThreshold
	}
	if policy.InodeMonitorPeriod == 0 {
		policy.InodeMonitorPeriod = c.defaultChainPolicies.EventService.InodeMonitorPeriod
	}

	return policy
}

func addMissingRetryOpts(opts retry.Opts, defaultOpts retry.Opts) retry.Opts {
	empty := retry.Opts{}

	if opts.InitialBackoff == empty.InitialBackoff {
		opts.InitialBackoff = defaultOpts.InitialBackoff
	}

	if opts.BackoffFactor == empty.BackoffFactor {
		opts.BackoffFactor = defaultOpts.BackoffFactor
	}

	if opts.MaxBackoff == empty.MaxBackoff {
		opts.MaxBackoff = defaultOpts.MaxBackoff
	}

	if len(opts.RetryableCodes) == len(empty.RetryableCodes) {
		opts.RetryableCodes = defaultOpts.RetryableCodes
	}

	return opts
}

func isEmpty(opts retry.Opts) bool {

	empty := retry.Opts{}
	if opts.Attempts == empty.Attempts &&
		opts.InitialBackoff == empty.InitialBackoff &&
		opts.BackoffFactor == empty.BackoffFactor &&
		opts.MaxBackoff == empty.MaxBackoff &&
		len(opts.RetryableCodes) == len(empty.RetryableCodes) {
		return true
	}

	return false
}

func (c *EndpointConfig) loadAllInodeConfigs(networkConfig *ichain.NetworkConfig, entityInodes map[string]InodeConfig) error {
	networkConfig.Inodes = make(map[string]ichain.InodeConfig)
	for name, inodeConfig := range entityInodes {
		if name == defaultEntity || c.isInodeToBeIgnored(name) {
			//filter default and ignored inodes
			continue
		}
		tlsCert, _, err := inodeConfig.TLSCACerts.TLSCert()
		if err != nil {
			return errors.WithMessage(err, "failed to load inode network config")
		}
		networkConfig.Inodes[name] = c.addMissingInodeConfigItems(name, ichain.InodeConfig{
			URL:         inodeConfig.URL,
			GRPCOptions: inodeConfig.GRPCOptions,
			TLSCACert:   tlsCert,
		})
	}
	return nil
}

func (c *EndpointConfig) loadAllConsenserConfigs(networkConfig *ichain.NetworkConfig, entityConsensers map[string]ConsenserConfig) error {
	networkConfig.Consensers = make(map[string]ichain.ConsenserConfig)
	for name, consenserConfig := range entityConsensers {
		if name == defaultEntity || c.isConsenserToBeIgnored(name) {
			//filter default and ignored consensers
			continue
		}
		tlsCert, _, err := consenserConfig.TLSCACerts.TLSCert()
		if err != nil {
			return errors.WithMessage(err, "failed to load consenser network config")
		}
		networkConfig.Consensers[name] = c.addMissingConsenserConfigItems(name, ichain.ConsenserConfig{
			URL:         consenserConfig.URL,
			GRPCOptions: consenserConfig.GRPCOptions,
			TLSCACert:   tlsCert,
		})
	}
	return nil
}

func (c *EndpointConfig) addMissingInodeConfigItems(name string, config ichain.InodeConfig) ichain.InodeConfig {

	// inode URL
	if config.URL == "" {
		if c.defaultInodeConfig.URL == "" {
			config.URL = name + ":" + strconv.Itoa(defaultInodeListenPort)
		} else {
			config.URL = c.defaultInodeConfig.URL
		}
	}

	//tls ca certs
	if config.TLSCACert == nil {
		config.TLSCACert = c.defaultInodeConfig.TLSCACert
	}

	//if no grpc opts found
	if len(config.GRPCOptions) == 0 {
		config.GRPCOptions = c.defaultInodeConfig.GRPCOptions
		return config
	}

	//missing grpc opts
	for name, val := range c.defaultInodeConfig.GRPCOptions {
		_, ok := config.GRPCOptions[name]
		if !ok {
			config.GRPCOptions[name] = val
		}
	}

	return config
}

func (c *EndpointConfig) addMissingConsenserConfigItems(name string, config ichain.ConsenserConfig) ichain.ConsenserConfig {
	// consenser URL
	if config.URL == "" {
		if c.defaultConsenserConfig.URL == "" {
			config.URL = name + ":" + strconv.Itoa(defaultConsenserListenPort)
		} else {
			config.URL = c.defaultConsenserConfig.URL
		}
	}

	//tls ca certs
	if config.TLSCACert == nil {
		config.TLSCACert = c.defaultConsenserConfig.TLSCACert
	}

	//if no grpc opts found
	if len(config.GRPCOptions) == 0 {
		config.GRPCOptions = c.defaultConsenserConfig.GRPCOptions
		return config
	}

	//missing grpc opts
	for name, val := range c.defaultConsenserConfig.GRPCOptions {
		_, ok := config.GRPCOptions[name]
		if !ok {
			config.GRPCOptions[name] = val
		}
	}

	return config
}

func (c *EndpointConfig) loadDefaultConsenser(configEntity *endpointConfigEntity) error {
	defaultEntityConsenser, ok := configEntity.Consensers[defaultEntity]
	if !ok {
		defaultEntityConsenser = ConsenserConfig{
			GRPCOptions: make(map[string]interface{}),
		}
	}

	c.defaultConsenserConfig = ichain.ConsenserConfig{
		GRPCOptions: defaultEntityConsenser.GRPCOptions,
	}

	//set defaults for missing grpc opts

	//keep-alive-time
	_, ok = c.defaultConsenserConfig.GRPCOptions["keep-alive-time"]
	if !ok {
		c.defaultConsenserConfig.GRPCOptions["keep-alive-time"] = defaultKeepAliveTime
	}

	//keep-alive-timeout
	_, ok = c.defaultConsenserConfig.GRPCOptions["keep-alive-timeout"]
	if !ok {
		c.defaultConsenserConfig.GRPCOptions["keep-alive-timeout"] = defaultKeepAliveTimeout
	}

	//keep-alive-permit
	_, ok = c.defaultConsenserConfig.GRPCOptions["keep-alive-permit"]
	if !ok {
		c.defaultConsenserConfig.GRPCOptions["keep-alive-permit"] = defaultKeepAlivePermit
	}

	//fail-fast
	_, ok = c.defaultConsenserConfig.GRPCOptions["fail-fast"]
	if !ok {
		c.defaultConsenserConfig.GRPCOptions["fail-fast"] = defaultFailFast
	}

	//allow-insecure
	_, ok = c.defaultConsenserConfig.GRPCOptions["allow-insecure"]
	if !ok {
		c.defaultConsenserConfig.GRPCOptions["allow-insecure"] = defaultAllowInsecure
	}

	var err error
	c.defaultConsenserConfig.TLSCACert, _, err = defaultEntityConsenser.TLSCACerts.TLSCert()
	if err != nil {
		return errors.WithMessage(err, "failed to load default consenser network config")
	}

	return nil
}

func (c *EndpointConfig) loadDefaultChainPolicies(configEntity *endpointConfigEntity) {

	var defaultChPolicies ichain.ChainPolicies
	defaultChain, ok := configEntity.Chains[defaultEntity]
	if !ok {
		defaultChPolicies = c.getChainPolicies(defaultChainPolicies)
	} else {
		defaultChPolicies = c.getChainPolicies(&defaultChain.Policies)
	}

	c.loadDefaultDiscoveryPolicy(&defaultChPolicies.Discovery)
	c.loadDefaultSelectionPolicy(&defaultChPolicies.Selection)
	c.loadDefaultQueryChainPolicy(&defaultChPolicies.QueryChainConfig)
	c.loadDefaultEventServicePolicy(&defaultChPolicies.EventService)

	c.defaultChainPolicies = defaultChPolicies

}

func (c *EndpointConfig) loadDefaultDiscoveryPolicy(policy *ichain.DiscoveryPolicy) {
	if policy.MaxTargets == 0 {
		policy.MaxTargets = defaultMaxTargets
	}

	if policy.MinResponses == 0 {
		policy.MinResponses = defaultMinResponses
	}

	if len(policy.RetryOpts.RetryableCodes) == 0 {
		policy.RetryOpts.RetryableCodes = defaultDiscoveryRetryableCodes
	}
}

func (c *EndpointConfig) loadDefaultSelectionPolicy(policy *ichain.SelectionPolicy) {
	if policy.SortingStrategy == "" {
		policy.SortingStrategy = ichain.BlockHeightPriority
	}

	if policy.Balancer == "" {
		policy.Balancer = ichain.RoundRobin
	}

	if policy.BlockHeightLagThreshold == 0 {
		policy.BlockHeightLagThreshold = defaultBlockHeightLagThreshold
	}
}

func (c *EndpointConfig) loadDefaultQueryChainPolicy(policy *ichain.QueryChainConfigPolicy) {
	if policy.MaxTargets == 0 {
		policy.MaxTargets = defaultMaxTargets
	}

	if policy.MinResponses == 0 {
		policy.MinResponses = defaultMinResponses
	}
}

func (c *EndpointConfig) loadDefaultEventServicePolicy(policy *ichain.EventServicePolicy) {
	if policy.ResolverStrategy == "" {
		policy.ResolverStrategy = defaultResolverStrategy
	}

	if policy.MinBlockHeightResolverMode == "" {
		policy.MinBlockHeightResolverMode = defaultMinBlockHeightResolverMode
	}

	if policy.Balancer == "" {
		policy.Balancer = defaultBalancer
	}

	if policy.BlockHeightLagThreshold == 0 {
		policy.BlockHeightLagThreshold = defaultBlockHeightLagThreshold
	}

	if policy.ReconnectBlockHeightLagThreshold == 0 {
		policy.ReconnectBlockHeightLagThreshold = defaultReconnectBlockHeightLagThreshold
	}

	if policy.InodeMonitorPeriod == 0 {
		policy.InodeMonitorPeriod = defaultInodeMonitorPeriod
	}
}

func (c *EndpointConfig) loadDefaultInode(configEntity *endpointConfigEntity) error {

	defaultEntityInode, ok := configEntity.Inodes[defaultEntity]
	if !ok {
		defaultEntityInode = InodeConfig{
			GRPCOptions: make(map[string]interface{}),
		}
	}

	c.defaultInodeConfig = ichain.InodeConfig{
		GRPCOptions: defaultEntityInode.GRPCOptions,
	}

	//set defaults for missing grpc opts

	//keep-alive-time
	_, ok = c.defaultInodeConfig.GRPCOptions["keep-alive-time"]
	if !ok {
		c.defaultInodeConfig.GRPCOptions["keep-alive-time"] = defaultKeepAliveTime
	}

	//keep-alive-timeout
	_, ok = c.defaultInodeConfig.GRPCOptions["keep-alive-timeout"]
	if !ok {
		c.defaultInodeConfig.GRPCOptions["keep-alive-timeout"] = defaultKeepAliveTimeout
	}

	//keep-alive-permit
	_, ok = c.defaultInodeConfig.GRPCOptions["keep-alive-permit"]
	if !ok {
		c.defaultInodeConfig.GRPCOptions["keep-alive-permit"] = defaultKeepAlivePermit
	}

	//fail-fast
	_, ok = c.defaultInodeConfig.GRPCOptions["fail-fast"]
	if !ok {
		c.defaultInodeConfig.GRPCOptions["fail-fast"] = defaultFailFast
	}

	//allow-insecure
	_, ok = c.defaultInodeConfig.GRPCOptions["allow-insecure"]
	if !ok {
		c.defaultInodeConfig.GRPCOptions["allow-insecure"] = defaultAllowInsecure
	}

	var err error
	c.defaultInodeConfig.TLSCACert, _, err = defaultEntityInode.TLSCACerts.TLSCert()
	if err != nil {
		return errors.WithMessage(err, "failed to load default inode network config")
	}

	return nil
}

// loadAllTLSConfig 加载所有tls配置
func (c *EndpointConfig) loadAllTLSConfig(configEntity *endpointConfigEntity) error {
	err := c.loadClientTLSConfig(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load client TLSConfig ")
	}

	err = c.loadOrgTLSConfig(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load org TLSConfig ")
	}

	err = c.loadConsenserInodeTLSConfig(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load consenser/inode TLSConfig ")
	}

	err = c.loadTLSClientCerts(configEntity)
	if err != nil {
		return errors.WithMessage(err, "failed to load TLS client certs ")
	}
	return nil
}

// loadClientTLSConfig 加载客户端tls配置
func (c *EndpointConfig) loadClientTLSConfig(configEntity *endpointConfigEntity) error {
	//Clients Config
	//resolve paths and org name
	configEntity.Client.Organization = strings.ToLower(configEntity.Client.Organization)
	configEntity.Client.TLSCerts.Client.Key.Path = pathvar.Subst(configEntity.Client.TLSCerts.Client.Key.Path)
	configEntity.Client.TLSCerts.Client.Cert.Path = pathvar.Subst(configEntity.Client.TLSCerts.Client.Cert.Path)

	//pre load client key and cert bytes
	err := configEntity.Client.TLSCerts.Client.Key.LoadBytes()
	if err != nil {
		return errors.WithMessage(err, "failed to load client key")
	}

	err = configEntity.Client.TLSCerts.Client.Cert.LoadBytes()
	if err != nil {
		return errors.WithMessage(err, "failed to load client cert")
	}

	return nil
}

// loadOrgTLSConfig 加载组织tls配置
func (c *EndpointConfig) loadOrgTLSConfig(configEntity *endpointConfigEntity) error {
	//Organizations Config
	for org, orgConfig := range configEntity.Organizations {
		for user, userConfig := range orgConfig.Users {
			//resolve paths
			userConfig.Key.Path = pathvar.Subst(userConfig.Key.Path)
			userConfig.Cert.Path = pathvar.Subst(userConfig.Cert.Path)
			//pre load key and cert bytes
			err := userConfig.Key.LoadBytes()
			if err != nil {
				return errors.WithMessage(err, "failed to load org key")
			}

			err = userConfig.Cert.LoadBytes()
			if err != nil {
				return errors.WithMessage(err, "failed to load org cert")
			}
			fmt.Println("加载证书字节\n：", userConfig.Cert.Bytes())
			orgConfig.Users[user] = userConfig
		}
		configEntity.Organizations[org] = orgConfig
	}

	return nil
}

// loadTLSConfig 加载共识节点tls配置
func (c *EndpointConfig) loadConsenserInodeTLSConfig(configEntity *endpointConfigEntity) error {
	//Consensers Config
	for consenser, consenserConfig := range configEntity.Consensers {
		//resolve paths
		consenserConfig.TLSCACerts.Path = pathvar.Subst(consenserConfig.TLSCACerts.Path)
		//pre load key and cert bytes
		err := consenserConfig.TLSCACerts.LoadBytes()
		if err != nil {
			return errors.WithMessage(err, "failed to load consenser cert")
		}

		configEntity.Consensers[consenser] = consenserConfig
	}

	//Inode Config
	for inode, inodeConfig := range configEntity.Inodes {
		//resolve paths
		inodeConfig.TLSCACerts.Path = pathvar.Subst(inodeConfig.TLSCACerts.Path)
		//pre load key and cert bytes
		err := inodeConfig.TLSCACerts.LoadBytes()
		if err != nil {
			return errors.WithMessage(err, "failed to load inode cert")
		}
		configEntity.Inodes[inode] = inodeConfig
	}

	return nil
}

func (c *EndpointConfig) loadInodeConfigsByOrg() {

	c.inodeConfigsByOrg = make(map[string][]ichain.InodeConfig)

	for orgName, orgConfig := range c.networkConfig.Organizations {
		orgInodes := orgConfig.Inodes
		inodes := []ichain.InodeConfig{}

		for _, inodeName := range orgInodes {
			p, ok := c.tryMatchingInodeConfig(inodeName, false)
			if !ok {
				continue
			}

			if err := c.verifyInodeConfig(p, inodeName, endpoint.IsTLSEnabled(p.URL)); err != nil {
				continue
			}

			inodes = append(inodes, *p)
		}
		c.inodeConfigsByOrg[strings.ToLower(orgName)] = inodes
	}

}

func (c *EndpointConfig) loadNetworkInodes() {

	var netInodes []ichain.NetworkInode
	for org, inodeConfigs := range c.inodeConfigsByOrg {

		orgConfig, ok := c.networkConfig.Organizations[org]
		if !ok {
			continue
		}

		for _, inodeConfig := range inodeConfigs {
			netInodes = append(netInodes, ichain.NetworkInode{InodeConfig: inodeConfig, IAMID: orgConfig.IAMID})
		}
	}

	c.networkInodes = netInodes
}

func (c *EndpointConfig) loadConsenserConfigs() error {

	consenserConfigs := []ichain.ConsenserConfig{}
	for name := range c.networkConfig.Consensers {

		matchedConsenser, ok, ignoreConsenser := c.tryMatchingConsenserConfig(name, false)
		if !ok || ignoreConsenser {
			continue
		}

		if matchedConsenser.TLSCACert == nil && !c.backend.GetBool("client.tlsCerts.systemCertPool") {
			//check for TLS config only if secured connection is enabled
			allowInSecure := matchedConsenser.GRPCOptions["allow-insecure"] == true
			if endpoint.AttemptSecured(matchedConsenser.URL, allowInSecure) {
				return errors.Errorf("Consenser has no certs configured. Make sure TLSCACerts.Pem or TLSCACerts.Path is set for %s", matchedConsenser.URL)
			}
		}

		consenserConfigs = append(consenserConfigs, *matchedConsenser)
	}
	c.consenserConfigs = consenserConfigs
	return nil
}

func (c *EndpointConfig) loadChainInodes() error {

	chainInodesByChain := make(map[string][]ichain.ChainInode)

	for chainID, chainConfig := range c.networkConfig.Chains {
		inodes := []ichain.ChainInode{}
		for inodeName, chInodeConfig := range chainConfig.Inodes {
			p, ok := c.tryMatchingInodeConfig(strings.ToLower(inodeName), false)
			if !ok {
				continue
			}

			if err := c.verifyInodeConfig(p, inodeName, endpoint.IsTLSEnabled(p.URL)); err != nil {
				logger.Debugf("Verify InodeConfig failed for inode [%s], cause : [%s]", inodeName, err)
				return err
			}

			iamID, ok := c.inodeIAMID(inodeName)
			if !ok {
				return errors.Errorf("unable to find IAM ID for inode : %s", inodeName)
			}

			networkInode := ichain.NetworkInode{InodeConfig: *p, IAMID: iamID}

			inode := ichain.ChainInode{InodeChainConfig: chInodeConfig, NetworkInode: networkInode}

			inodes = append(inodes, inode)
		}
		chainInodesByChain[strings.ToLower(chainID)] = inodes
	}

	c.chainInodesByChain = chainInodesByChain

	return nil
}

func (c *EndpointConfig) loadChainConsensers() error {

	chainConsensersByChain := make(map[string][]ichain.ConsenserConfig)

	for chainID, chainConfig := range c.networkConfig.Chains {
		consensers := []ichain.ConsenserConfig{}
		for _, consenserName := range chainConfig.Consensers {

			consenser, ok, ignoreConsenser := c.tryMatchingConsenserConfig(strings.ToLower(consenserName), false)
			if ignoreConsenser {
				continue
			}

			if !ok {
				return errors.Errorf("Could not find Consenser Config for chain consenser [%s]", consenserName)
			}
			consensers = append(consensers, *consenser)
		}
		chainConsensersByChain[strings.ToLower(chainID)] = consensers
	}

	c.chainConsensersByChain = chainConsensersByChain

	return nil
}

func (c *EndpointConfig) loadTLSCertPool() error {

	var err error
	c.tlsCertPool, err = commtls.NewCertPool(c.backend.GetBool("client.tlsCerts.systemCertPool"))
	if err != nil {
		return errors.WithMessage(err, "failed to create cert pool")
	}

	certs := c.loadTLSCerts()
	c.tlsCertPool.Add(certs...)

	if _, err := c.tlsCertPool.Get(); err != nil {
		return errors.WithMessage(err, "cert pool load failed")
	}
	return nil
}

func (c *EndpointConfig) loadTLSClientCerts(configEntity *endpointConfigEntity) error {

	var clientCerts gmtls.Certificate
	cb := configEntity.Client.TLSCerts.Client.Cert.Bytes()
	if len(cb) == 0 {
		c.tlsClientCerts = []gmtls.Certificate{clientCerts}
		return nil
	}

	// 在配置中读取tls证书
	tlsCerts, err := c.loadPrivateKeyFromConfig(&configEntity.Client, cb)
	if err != nil {
		return errors.WithMessage(err, "failed to load TLS client certs")
	}
	c.tlsClientCerts = tlsCerts
	return nil
}

func (c *EndpointConfig) isInodeToBeIgnored(inodeName string) bool {
	for _, matcher := range c.inodeMatchers {
		if matcher.regex.MatchString(inodeName) {
			return matcher.matchConfig.IgnoreEndpoint
		}
	}
	return false
}

func (c *EndpointConfig) isConsenserToBeIgnored(consenserName string) bool {
	for _, matcher := range c.consenserMatchers {
		if matcher.regex.MatchString(consenserName) {
			return matcher.matchConfig.IgnoreEndpoint
		}
	}
	return false
}

func (c *EndpointConfig) tryMatchingInodeConfig(inodeSearchKey string, searchByURL bool) (*ichain.InodeConfig, bool) {

	//loop over inode entity matchers to find the matching inode
	for _, matcher := range c.inodeMatchers {
		if matcher.regex.MatchString(inodeSearchKey) {
			return c.matchInode(inodeSearchKey, matcher)
		}
		logger.Debugf("Inode [%s] did not match using matcher [%s]", inodeSearchKey, matcher.regex.String())
	}

	//direct lookup if inode matchers are not configured or no matchers matched
	inodeConfig, ok := c.networkConfig.Inodes[strings.ToLower(inodeSearchKey)]
	if ok {
		return &inodeConfig, true
	}

	if searchByURL {
		//lookup by URL
		for _, staticInodeConfig := range c.networkConfig.Inodes {
			if strings.EqualFold(staticInodeConfig.URL, inodeSearchKey) {
				return &ichain.InodeConfig{
					URL:         staticInodeConfig.URL,
					GRPCOptions: staticInodeConfig.GRPCOptions,
					TLSCACert:   staticInodeConfig.TLSCACert,
				}, true
			}
		}
	}

	if searchByURL && strings.Contains(inodeSearchKey, ":") {
		return &ichain.InodeConfig{
			URL:         inodeSearchKey,
			GRPCOptions: c.defaultInodeConfig.GRPCOptions,
			TLSCACert:   c.defaultInodeConfig.TLSCACert,
		}, true
	}

	return nil, false
}

func (c *EndpointConfig) matchInode(inodeSearchKey string, matcher matcherEntry) (*ichain.InodeConfig, bool) {

	if matcher.matchConfig.IgnoreEndpoint {
		logger.Debugf("Ignoring inode `%s` since entity matcher IgnoreEndpoint flag is on", inodeSearchKey)
		return nil, false
	}

	mappedHost := c.regexMatchAndReplace(matcher.regex, inodeSearchKey, matcher.matchConfig.MappedHost)

	matchedInode := c.getMappedInode(mappedHost)
	if matchedInode == nil {
		logger.Debugf("Could not find mapped host [%s] for inode [%s]", matcher.matchConfig.MappedHost, inodeSearchKey)
		return nil, false
	}

	//URLSubstitutionExp if found use from entity matcher otherwise use from mapped host
	if matcher.matchConfig.URLSubstitutionExp != "" {
		matchedInode.URL = c.regexMatchAndReplace(matcher.regex, inodeSearchKey, matcher.matchConfig.URLSubstitutionExp)
	}

	//SSLTargetOverrideURLSubstitutionExp if found use from entity matcher otherwise use from mapped host
	if matcher.matchConfig.SSLTargetOverrideURLSubstitutionExp != "" {
		matchedInode.GRPCOptions["ssl-target-name-override"] = c.regexMatchAndReplace(matcher.regex, inodeSearchKey, matcher.matchConfig.SSLTargetOverrideURLSubstitutionExp)
	}

	//if no URL to add from entity matcher or from mapped host or from default inode
	if matchedInode.URL == "" {
		matchedInode.URL = c.getDefaultMatchingURL(inodeSearchKey)
	}

	return matchedInode, true
}

// getDefaultMatchingURL if search key is a URL then returns search key as URL otherwise returns empty
func (c *EndpointConfig) getDefaultMatchingURL(searchKey string) string {
	if strings.Contains(searchKey, ":") {
		return searchKey
	}
	return ""
}

func (c *EndpointConfig) getMappedInode(host string) *ichain.InodeConfig {
	//Get the inodeConfig from mapped host
	inodeConfig, ok := c.networkConfig.Inodes[strings.ToLower(host)]
	if !ok {
		inodeConfig = c.defaultInodeConfig
	}

	mappedConfig := ichain.InodeConfig{
		URL:         inodeConfig.URL,
		TLSCACert:   inodeConfig.TLSCACert,
		GRPCOptions: make(map[string]interface{}),
	}

	for key, val := range inodeConfig.GRPCOptions {
		mappedConfig.GRPCOptions[key] = val
	}

	return &mappedConfig
}

func (c *EndpointConfig) tryMatchingConsenserConfig(consenserSearchKey string, searchByURL bool) (*ichain.ConsenserConfig, bool, bool) {

	//loop over consenser entity matchers to find the matching consenser
	for _, matcher := range c.consenserMatchers {
		if matcher.regex.MatchString(consenserSearchKey) {
			return c.matchConsenser(consenserSearchKey, matcher)
		}
		logger.Debugf("Consenser [%s] did not match using matcher [%s]", consenserSearchKey, matcher.regex.String())
	}

	//direct lookup if consenser matchers are not configured or no matchers matched
	consenser, ok := c.networkConfig.Consensers[strings.ToLower(consenserSearchKey)]
	if ok {
		return &consenser, true, false
	}

	if searchByURL {
		//lookup by URL
		for _, consenserCfg := range c.ConsensersConfig() {
			if strings.EqualFold(consenserCfg.URL, consenserSearchKey) {
				return &ichain.ConsenserConfig{
					URL:         consenserCfg.URL,
					GRPCOptions: consenserCfg.GRPCOptions,
					TLSCACert:   consenserCfg.TLSCACert,
				}, true, false
			}
		}
	}

	//In case of URL search, return default consenser config where URL=SearchKey
	if searchByURL && strings.Contains(consenserSearchKey, ":") {
		return &ichain.ConsenserConfig{
			URL:         consenserSearchKey,
			GRPCOptions: c.defaultConsenserConfig.GRPCOptions,
			TLSCACert:   c.defaultConsenserConfig.TLSCACert,
		}, true, false
	}

	return nil, false, false
}

func (c *EndpointConfig) matchConsenser(consenserSearchKey string, matcher matcherEntry) (*ichain.ConsenserConfig, bool, bool) {

	if matcher.matchConfig.IgnoreEndpoint {
		logger.Debugf(" Ignoring consenser `%s` since entity matcher IgnoreEndpoint flag is on", consenserSearchKey)
		// IgnoreEndpoint must force ignoring this matching consenser (weather found or not) and must be explicitly
		// mentioned. The third argument is explicitly used for this, all other cases will return false.
		return nil, false, true
	}

	mappedHost := c.regexMatchAndReplace(matcher.regex, consenserSearchKey, matcher.matchConfig.MappedHost)

	//Get the consenserConfig from mapped host
	matchedConsenser := c.getMappedConsenser(mappedHost)
	if matchedConsenser == nil {
		logger.Debugf("Could not find mapped host [%s] for consenser [%s]", matcher.matchConfig.MappedHost, consenserSearchKey)
		return nil, false, false
	}

	//URLSubstitutionExp if found use from entity matcher otherwise use from mapped host
	if matcher.matchConfig.URLSubstitutionExp != "" {
		matchedConsenser.URL = c.regexMatchAndReplace(matcher.regex, consenserSearchKey, matcher.matchConfig.URLSubstitutionExp)
	}

	//SSLTargetOverrideURLSubstitutionExp if found use from entity matcher otherwise use from mapped host
	if matcher.matchConfig.SSLTargetOverrideURLSubstitutionExp != "" {
		matchedConsenser.GRPCOptions["ssl-target-name-override"] = c.regexMatchAndReplace(matcher.regex, consenserSearchKey, matcher.matchConfig.SSLTargetOverrideURLSubstitutionExp)
	}

	//if no URL to add from entity matcher or from mapped host or from default inode
	if matchedConsenser.URL == "" {
		matchedConsenser.URL = c.getDefaultMatchingURL(consenserSearchKey)
	}

	return matchedConsenser, true, false
}

func (c *EndpointConfig) getMappedConsenser(host string) *ichain.ConsenserConfig {
	//Get the inodeConfig from mapped host
	consenserConfig, ok := c.networkConfig.Consensers[strings.ToLower(host)]
	if !ok {
		consenserConfig = c.defaultConsenserConfig
	}

	mappedConfig := ichain.ConsenserConfig{
		URL:         consenserConfig.URL,
		TLSCACert:   consenserConfig.TLSCACert,
		GRPCOptions: make(map[string]interface{}),
	}

	for key, val := range consenserConfig.GRPCOptions {
		mappedConfig.GRPCOptions[key] = val
	}

	return &mappedConfig
}

func (c *EndpointConfig) compileMatchers() error {

	entMatchers := entityMatchers{}

	err := c.backend.UnmarshalKey("entityMatchers", &entMatchers.matchers)
	logger.Debugf("Matchers are: %+v", entMatchers)
	if err != nil {
		return errors.WithMessage(err, "failed to parse 'entMatchers' config item")
	}

	//return no error if entityMatchers is not configured
	if len(entMatchers.matchers) == 0 {
		logger.Debug("Entity matchers are not configured")
		return nil
	}

	err = c.compileAllMatchers(&entMatchers)
	if err != nil {
		return err
	}

	c.entityMatchers = &entMatchers
	return nil
}

func (c *EndpointConfig) compileAllMatchers(matcherConfig *entityMatchers) error {

	var err error
	if len(matcherConfig.matchers["chain"]) > 0 {
		c.chainMatchers, err = c.groupAllMatchers(matcherConfig.matchers["chain"])
		if err != nil {
			return err
		}
	}

	if len(matcherConfig.matchers["consenser"]) > 0 {
		c.consenserMatchers, err = c.groupAllMatchers(matcherConfig.matchers["consenser"])
		if err != nil {
			return err
		}
	}

	if len(matcherConfig.matchers["inode"]) > 0 {
		c.inodeMatchers, err = c.groupAllMatchers(matcherConfig.matchers["inode"])
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *EndpointConfig) groupAllMatchers(matchers []MatchConfig) ([]matcherEntry, error) {
	matcherEntries := make([]matcherEntry, len(matchers))
	for i, v := range matchers {
		regex, err := regexp.Compile(v.Pattern)
		if err != nil {
			return nil, err
		}
		matcherEntries[i] = matcherEntry{regex: regex, matchConfig: v}
	}
	return matcherEntries, nil
}

func (c *EndpointConfig) verifyInodeConfig(p *ichain.InodeConfig, inodeName string, tlsEnabled bool) error {
	if p == nil || p.URL == "" {
		return errors.Errorf("URL does not exist or empty for inode %s", inodeName)
	}
	if tlsEnabled && p.TLSCACert == nil && !c.backend.GetBool("client.tlsCerts.systemCertPool") {
		return errors.Errorf("tls.certificate does not exist or empty for inode %s", inodeName)
	}
	return nil
}

func (c *EndpointConfig) loadTLSCerts() []*x509GM.Certificate {
	var certs []*x509GM.Certificate

	for _, inode := range c.networkInodes {
		if inode.TLSCACert != nil {
			certs = append(certs, inode.TLSCACert)
		}
	}
	for _, consenser := range c.consenserConfigs {
		if consenser.TLSCACert != nil {
			certs = append(certs, consenser.TLSCACert)
		}
	}
	return certs
}

// ResetNetworkConfig clears network config cache
func (c *EndpointConfig) ResetNetworkConfig() error {
	c.networkConfig = nil
	return c.loadEndpointConfiguration()
}

// InodeIAMID returns iam that inode belongs to
func (c *EndpointConfig) inodeIAMID(name string) (string, bool) {
	var iamID string
	// Find organisation/iam that inode belongs to
	for _, org := range c.networkConfig.Organizations {
		for i := 0; i < len(org.Inodes); i++ {
			if strings.EqualFold(org.Inodes[i], name) {
				// inode belongs to this org add org iam
				iamID = org.IAMID
				break
			} else {
				inode, ok := c.findMatchingInode(org.Inodes[i])
				if ok && strings.EqualFold(inode, name) {
					iamID = org.IAMID
					break
				}
			}
		}
	}

	return iamID, iamID != ""
}

func (c *EndpointConfig) findMatchingInode(inodeName string) (string, bool) {

	//Return if no inodeMatchers are configured
	if len(c.inodeMatchers) == 0 {
		return "", false
	}

	//loop over inodeentityMatchers to find the matching inode
	for _, matcher := range c.inodeMatchers {
		if matcher.regex.MatchString(inodeName) {
			return matcher.matchConfig.MappedHost, true
		}
	}

	return "", false
}

// regexMatchAndReplace if 'repl' has $ then perform regex.ReplaceAllString otherwise return 'repl'
func (c *EndpointConfig) regexMatchAndReplace(regex *regexp.Regexp, src, repl string) string {
	if strings.Contains(repl, "$") {
		return regex.ReplaceAllString(src, repl)
	}
	return repl
}

// inodeChainConfigHookFunc returns hook function for unmarshalling 'ichain.InodeChainConfig'
// Rule : default set to 'true' if not provided in config
func inodeChainConfigHookFunc() mapstructure.DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data interface{}) (interface{}, error) {

		//Run through each InodeChainConfig, create empty config map if value is nil
		if t == reflect.TypeOf(map[string]InodeChainConfig{}) {
			dataMap, ok := data.(map[string]interface{})
			if ok {
				for k, v := range dataMap {
					if v == nil {
						// Make an empty map. It will be filled in with defaults
						// in other hook below
						dataMap[k] = make(map[string]interface{})
					}
				}
				return dataMap, nil
			}
		}

		//If target is of type 'fab.InodeChainConfig', fill in defaults if not already specified
		if t == reflect.TypeOf(InodeChainConfig{}) {
			dataMap, ok := data.(map[string]interface{})
			if ok {
				setDefault(dataMap, "endorsinginode", true)
				setDefault(dataMap, "contractquery", true)
				setDefault(dataMap, "ledgerquery", true)
				setDefault(dataMap, "eventsource", true)

				return dataMap, nil
			}
		}

		return data, nil
	}
}

// setDefault sets default value provided to map if given key not found
func setDefault(dataMap map[string]interface{}, key string, defaultVal interface{}) {
	_, ok := dataMap[key]
	if !ok {
		dataMap[key] = defaultVal
	}
}

// detectDeprecatedConfigOptions detects deprecated config options and prints warnings
// currently detects: if chains.consensers are defined
func detectDeprecatedNetworkConfig(endpointConfig *EndpointConfig) {

	if endpointConfig.networkConfig == nil {
		return
	}

	//detect if chains consensers are mentioned
	for _, v := range endpointConfig.networkConfig.Chains {
		if len(v.Consensers) > 0 {
			logger.Warn("Getting consensers from endpoint config chains.consenser is deprecated," +
				" use entity matchers to override consenser configuration")
			break
		}
	}
}
